﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Numerics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using NodeBasedSkillEditor.Model;
using NodeBasedSkillEditor.View;
using NodeBasedSkillEditor.ViewModel;

namespace NodeBasedSkillEditor.Windows
{
    public partial class NodeInspector : Window
    {
        public readonly static double FieldHeight = 20;
        public readonly static double UIFontSize = 14;
        public readonly static Thickness FieldMarin = new(2);


        public bool HasInputs {
            get { return (bool)GetValue(HasInputsProperty); }
            set { SetValue(HasInputsProperty, value); }
        }
        public static readonly DependencyProperty HasInputsProperty =
            DependencyProperty.Register("HasInputs", typeof(bool), typeof(NodeInspector),
                new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


        public event Action<uint, object, object> OnAnyValueChanged;
        private HashSet<VM_Port> _listoningPorts = new HashSet<VM_Port>();
        public event Action<uint, string, string> OnNodeDescriptionChanged;

        private SolidColorBrush _labelBrush = new SolidColorBrush(Colors.LightGray);
        private SolidColorBrush _groupTitleBrush = new SolidColorBrush(Colors.Gray);

        private VM_Node _vm { get => DataContext as VM_Node; }

        bool _needRefresh = false;
        HashSet<ColumnDefinition> _allLabelColumns = new();
        int _loadedLabelCount = 0;

        public NodeInspector()
        {
            InitializeComponent();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            ClearPortLisontersOnClosing();
            DescriptionField.ApplyValueFromInputBox();
            _vm.RelativeInspector = null;
            _vm.OnDescriptionChanged -= OnNodeDescriptionChanged;
            DataContext = null;
        }

        /// <summary>
        /// 注意，为了让使用者有良好的体验，在OnClosing函数里（也就是窗口被默认关闭的流程），会对DescriptionField刷新赋值
        /// 从而更新绑定的Port的Description，这个行为会产生一次操作历史
        /// 然而，当窗口因为意外原因被关闭时（例如关联的node被删除），也会触发DescriptionField更新，进而产生一个不需要的操作历史
        /// 不仅如此，还有把当前的历史步骤改成最新（因为新增了历史），这在历史回退、前进操作中发生node删除时就是错误的行为了
        /// 所以这里增加一个专用的接口，关闭窗口而不产生历史（因为默认的关闭流程不能被重载，而大多数天然的操作都会走默认关闭流程）
        /// </summary>
        public void CloseWithoutChangeHistory()
        {
            _vm.OnDescriptionChanged -= OnNodeDescriptionChanged;
            Close();
        }

        public void ChangedNodeViewModel(VM_Node vmNode)
        {
            if (_vm != null)
            {
                _vm.RelativeInspector = null;
            }
            vmNode.RelativeInspector = this;

            DataContext = vmNode;
            Title = vmNode.NodeName;

            vmNode.OnDescriptionChanged += OnNodeDescriptionChanged;
            RefreshContent();
        }

        public async void NeedRefresh()
        {
            if (_needRefresh)
            {
                return;
            }
            else
            {
                _needRefresh = true;
                await Task.Delay(1);
                RefreshContent();
                _needRefresh = false;
            }
        }

        void RefreshContent()
        {
            InputStack.Children.Clear();
            _allLabelColumns.Clear();
            _loadedLabelCount = 0;
            foreach (var p in _vm.AllPorts)
            {
                var currPort = p.Value;
                if (DAGHelper.ShouldPortBeVisibleInInspector(currPort))
                {
                    if (currPort.ParentCluster != null) { continue; }

                    if (currPort.IsExpandPort)
                    {
                        AddGroupField(InputStack.Children, currPort);
                        //GroupBox g = new();
                        //g.Header = $"{currPort.PortName} group";
                        //g.BorderThickness = new Thickness(0.125);
                        //g.BorderBrush = _labelBrush;
                        //g.Foreground = _labelBrush;
                        //g.Margin = new Thickness(0, 0, 0, 3);

                        //StackPanel s = new();
                        //g.Content = s;
                        //foreach (var subP in currPort.ParentGroup.SubPorts)
                        //{
                        //    AddField(s, subP);
                        //}
                        //InputStack.Children.Add(g);
                    }
                    else if (!currPort.IsNormalPortInGroup)
                    {
                        AddField(InputStack, currPort);
                    }
                }
            }
            foreach (var pcg in _vm.AllPortClusterGroups)
            {
                var currPCG = pcg.Value;
                var pcgGroupBox = CreateNewGroupBox(currPCG.ClusterGroupName);
                InputStack.Children.Add(pcgGroupBox);
                StackPanel groupStack = new();
                pcgGroupBox.Content = groupStack;
                for (int i = 0; i < currPCG.Clusters.Count; i++)
                {
                    var pc = currPCG.Clusters[i];
                    var pcGB = CreateNewGroupBox($"{currPCG.ClusterGroupName}{i}");
                    groupStack.Children.Add(pcGB);

                    StackPanel clusterStack = new();
                    pcGB.Content = clusterStack;
                    foreach (var p in pc.AllPorts)
                    {
                        if (DAGHelper.ShouldPortBeVisibleInInspector(p))
                        {
                            if (p.IsExpandPort)
                            {
                                AddGroupField(clusterStack.Children, p);
                            }
                            else if (!p.IsNormalPortInGroup)
                            {
                                AddField(clusterStack, p);
                            }
                        }
                    }
                }
            }
            HasInputs = InputStack.Children.Count > 0;
            InvalidateVisual();
        }

        GroupBox CreateNewGroupBox(string groupName)
        {
            GroupBox g = new();
            g.Header = groupName;
            g.BorderThickness = new Thickness(0.125);
            g.BorderBrush = _groupTitleBrush;
            g.Foreground = _groupTitleBrush;
            g.Margin = new Thickness(0, 0, 0, 3);
            return g;
        }

        void AddGroupField(UIElementCollection destUICollection, VM_Port expandPort)
        {
            var g = CreateNewGroupBox(expandPort.PortName);
            //GroupBox g = new();
            //g.Header = $"{expandPort.PortName} group";
            //g.BorderThickness = new Thickness(0.125);
            //g.BorderBrush = _labelBrush;
            //g.Foreground = _labelBrush;
            //g.Margin = new Thickness(0, 0, 0, 3);

            StackPanel s = new();
            g.Content = s;
            foreach (var subP in expandPort.ParentGroup.SubPorts)
            {
                AddField(s, subP);
            }
            destUICollection.Add(g);
        }

        void RefreshLabelWidth()
        {
            double labelWidth = 0;
            foreach (var cl in _allLabelColumns)
            {
                if (cl.ActualWidth > labelWidth)
                {
                    labelWidth = cl.ActualWidth;
                }
            }
            foreach (var cl in _allLabelColumns)
            {
                cl.Width = new GridLength(labelWidth);
            }
            _allLabelColumns.Clear();
        }

        void AddField(StackPanel parentStack, VM_Port portVM)
        {
            var fieldCtrl = CreateField(portVM);
            if (fieldCtrl != null)
            {
                var label = CreateLabel(portVM.PortFullName);
                var fieldGrid = CreateFieldCtrlGrid(label, fieldCtrl);
                parentStack.Children.Add(fieldGrid);
            }
        }

        Grid CreateFieldCtrlGrid(FrameworkElement label, FrameworkElement dataCtrl)
        {
            Grid fieldGrid = new();

            ColumnDefinition labelColum = new();
            labelColum.MinWidth = 60;
            labelColum.Width = GridLength.Auto;
            labelColum.Loaded += LabelColumLoaded;

            ColumnDefinition dataColum = new();

            fieldGrid.ColumnDefinitions.Add(labelColum);
            fieldGrid.ColumnDefinitions.Add(dataColum);

            fieldGrid.Children.Add(label);
            fieldGrid.Children.Add(dataCtrl);
            label.SetValue(Grid.ColumnProperty, 0);
            dataCtrl.SetValue(Grid.ColumnProperty, 1);

            _allLabelColumns.Add(labelColum);

            return fieldGrid;
        }

        private void LabelColumLoaded(object sender, RoutedEventArgs e)
        {
            var lableCol = sender as ColumnDefinition;
            if (lableCol != null)
            {
                _loadedLabelCount++;
                if (_loadedLabelCount == _allLabelColumns.Count)
                {
                    RefreshLabelWidth();
                }
            }
        }

        TextBlock CreateLabel(string labelName)
        {
            TextBlock label = new();
            label.Text = labelName;
            label.Height = FieldHeight;
            label.FontSize = UIFontSize;
            label.Foreground = new SolidColorBrush(Colors.LightGray);
            label.Margin = FieldMarin;
            label.VerticalAlignment = VerticalAlignment.Bottom;
            label.HorizontalAlignment = HorizontalAlignment.Left;
            return label;
        }

        FrameworkElement CreateField(VM_Port portVM)
        {
            if (portVM.IsListPort)
            {
                return null;
            }

            if (portVM.DataType == typeof(bool))
            {
                var dataCtrl = CreateCtrl<BooleanField>(!portVM.IsConnected);
                dataCtrl.SetValue(ForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, BooleanField.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType == typeof(int))
            {
                var dataCtrl = CreateCtrl<IntegerField>(!portVM.IsConnected);
                dataCtrl.SetValue(ForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, IntegerField.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType == typeof(float))
            {
                var dataCtrl = CreateCtrl<FloatField>(!portVM.IsConnected);
                dataCtrl.SetValue(ForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, FloatField.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType == typeof(Vector2))
            {
                var dataCtrl = CreateCtrl<Vector2Field>(!portVM.IsConnected);
                dataCtrl.SetValue(Vector2Field.LabelForegroundProperty, new SolidColorBrush(Colors.LightGray));
                dataCtrl.SetValue(Vector2Field.FieldForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, Vector2Field.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType == typeof(Vector3))
            {
                var dataCtrl = CreateCtrl<Vector3Field>(!portVM.IsConnected);
                dataCtrl.SetValue(Vector3Field.LabelForegroundProperty, new SolidColorBrush(Colors.LightGray));
                dataCtrl.SetValue(Vector3Field.FieldForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, Vector3Field.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType == typeof(string))
            {
                var dataCtrl = CreateCtrl<StringField>(!portVM.IsConnected);
                dataCtrl.SetValue(ForegroundProperty, new SolidColorBrush(Colors.White));
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, StringField.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            else if (portVM.DataType.IsEnum)
            {
                var portData = portVM.BindPortData as EnumPort;
                var dataCtrl = CreateCtrl<EnumerateField>(!portVM.IsConnected);
                dataCtrl.SetValue(ForegroundProperty, new SolidColorBrush(Colors.White));
                dataCtrl.SetValue(BackgroundProperty, new SolidColorBrush(Colors.DimGray));
                dataCtrl.Value = portData.Value;
                var dataBind = CreateBinding(portVM, "Value");
                BindingOperations.SetBinding(dataCtrl, EnumerateField.ValueProperty, dataBind);
                ListonToPort(portVM);
                //dataCtrl.OnValueChanged += (o, n) => OnAnyValueChanged?.Invoke(portVM.Id, o, n);
                return dataCtrl;
            }
            return null;
        }

        T CreateCtrl<T>(bool isEnabled) where T : Control, new()
        {
            var ctrl = new T();
            ctrl.Height = double.NaN;
            ctrl.FontSize = UIFontSize;
            ctrl.Margin = FieldMarin;
            ctrl.IsEnabled = isEnabled;
            return ctrl;
        }

        Binding CreateBinding(object source, string propPath)
        {
            Binding dataBind = new Binding();
            dataBind.Source = source;
            dataBind.Path = new PropertyPath(propPath);
            dataBind.Mode = BindingMode.TwoWay;
            dataBind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            return dataBind;
        }

        void ListonToPort(VM_Port port)
        {
            port.OnPortValueChanged += OnAnyValueChanged;
            _listoningPorts.Add(port);
        }

        void ClearPortLisontersOnClosing()
        {
            foreach (var p in _listoningPorts)
            {
                p.OnPortValueChanged -= OnAnyValueChanged;
            }
            _listoningPorts.Clear();
        }
    }
}
